/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.connection;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.connection.parser.EventEncodingException;
import com.hercules.android.receiver.connection.parser.MessageParser;
import com.hercules.android.receiver.model.commands.XREConnectCommand;
import com.hercules.android.receiver.model.events.XREEvent;

import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>
 * Helper class containing some help methods and constants.
 * </p>
 * <p>
 * AndroidAssembly1 changed class name
 * <p>
 * <strong>Thread Safety:</strong>This class is immutable and thread safe.
 * </p>
 *
 * @author morehappiness
 * @version 1.0
 */
public final class ConnectionUtil {
    /**
     * Private constructor.
     */
    private ConnectionUtil() {
        // do nothing
    }

    /**
     * Checks the parameter for null. If it's null, throw {@link IllegalArgumentException}.
     *
     * @param logger the logger to use
     * @param obj the object to check
     * @param name the name of the parameter
     * @param handler the exception handler to use
     * @throws IllegalArgumentException if the parameter is null and handler is null
     */
    public static void checkNull(Logger logger, Object obj, String name, ConnectionErrorHandler handler) {
        if (null == obj) {
            IllegalArgumentException e = new IllegalArgumentException("The " + name + " can not be null");

            if (null == handler) {
                logException(logger, e);
                throw e;
            }
            logAndHandleException(logger, e, handler);
        }
    }

    /**
     * Checks the parameter for null or empty. If it's null or empty, throw {@link IllegalArgumentException}.
     *
     * @param logger the logger to use
     * @param obj the object to check which should not be null
     * @param name the name of the parameter
     * @throws IllegalArgumentException if the parameter is null or empty
     */
    public static void checkNullOrEmpty(Logger logger, String obj, String name) {
        checkNull(logger, obj, name, null);

        if (obj.trim().length() == 0) {
            throw logException(logger, new IllegalArgumentException("The " + name + " can not be empty"));
        }
    }

    /**
     * Checks the parameter intVal is less than minimal or not. If the parameter intVal is less than minimal, throw
     * {@link IllegalArgumentException}.
     *
     * @param intVal the int value to check
     * @param minimal the minimal value
     * @param name the name of the parameter
     * @throws IllegalArgumentException if the parameter intVal is less than minimal
     */
    public static void checkInt(int intVal, int minimal, String name) {
        if (intVal < minimal) {
            throw new IllegalArgumentException("The " + name + " should not less than " + minimal);
        }
    }

    /**
     * Logs the exception message and stack trace.
     *
     * @param <T> the exception type
     * @param logger the logger to use
     * @param e the exception to log
     * @return the exception from the parameter e
     */
    public static <T extends Exception> T logException(Logger logger, T e) {
        if (null != logger) {
            // Create a StringWriter for printing out the stack trace
            StringWriter sr = new StringWriter();

            // Print the stack trace into the StringWriter
            PrintWriter pw = new PrintWriter(sr);

            e.printStackTrace(pw);

            StringBuffer sb = new StringBuffer();

            sb.append("Error Message:\n");
            sb.append(e.getMessage());
            sb.append("\nStack Trace:\n");
            sb.append(sr.getBuffer());

            logger.log(Level.SEVERE, sb.toString(), e);

            // close the resources related to the print writer
            pw.close();
        }

        return e;
    }

    /**
     * Logs the entrance of a method and all the input arguments.
     *
     * @param logger used to do the logging
     * @param methodName the name of the method
     * @param paramNames the name of the parameters
     * @param params the parameters
     */
    public static void logEntrance(Logger logger, String methodName, String[] paramNames, Object[] params) {
        StringBuffer logInfo = new StringBuffer("Enter into Method: ");

        logInfo.append(methodName);

        if (paramNames != null) {
            logInfo.append(", Parameters:");

            for (int i = 0; i < paramNames.length; i++) {
                logInfo.append(" [ ");
                logInfo.append(paramNames[i]);
                logInfo.append(" = ");
                logInfo.append(params[i]);
                logInfo.append(" ]");
            }
        }
        logger.log(Level.INFO, logInfo.toString());
    }

    /**
     * Logs the exit of a method and return value.
     *
     * @param logger used to do the logging
     * @param methodName the name of the method
     */
    public static void logExit(Logger logger, String methodName) {
        StringBuffer logInfo = new StringBuffer("Exit out Method: ");

        logInfo.append(methodName);

        logger.log(Level.INFO, logInfo.toString());
    }

    /**
     * Logs and handles the exception.
     *
     * @param <T> the exception type
     * @param logger used to do the logging
     * @param e the exception
     * @param exceptionHandler the exception handler
     */
    public static <T extends Exception> void logAndHandleException(Logger logger, T e,
        ConnectionErrorHandler exceptionHandler) {
        logException(logger, e);
        exceptionHandler.handleException(e);
    }

    /**
     * Logs and handles the exception.
     *
     * @param <T> the exception type
     * @param logger used to do the logging
     * @param clazz the calling object
     * @param methodName the calling method
     * @param e the exception
     * @param exceptionHandler the exception handler
     */
    public static <T extends Exception> void logAndHandle(Logger logger, Object clazz, String methodName, T e,
        ConnectionErrorHandler exceptionHandler) {
        LogUtil.error(logger, clazz, methodName, e);
        LogUtil.info(logger, clazz, methodName, "Handling connection exception {0}", e);

        exceptionHandler.handleException(e);
    }

    /**
     * Checks whether the sessionGUID is in the closedConnections or not. Returns true if the sessionGUID is not in the
     * closedConnections; otherwise returns false.
     *
     * @param closedConnections the closed connections list
     * @param sessionGUID the session GUID
     * @return true if the sessionGUID is not in the closedConnections; otherwise returns false
     */
    public static boolean isConnectionNotClosed(List<String> closedConnections, String sessionGUID) {
        synchronized (closedConnections) {
            if (closedConnections.contains(sessionGUID)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Adds the sessionGUID into the closedConnections.
     *
     * @param closedConnections the closed connections list
     * @param sessionGUID the session GUID
     */
    public static void addSessionGUID(List<String> closedConnections, String sessionGUID) {
        // add sessionGUID to these closedConnections list (in order for the thread to stop)
        // before accessing the closedConnections list lock on it
        synchronized (closedConnections) {
            closedConnections.add(sessionGUID);
        }
    }

    /**
     * Removes the sessionGUID from the closedConnections.
     *
     * @param closedConnections the closed connections list
     * @param sessionGUID the session GUID
     */
    public static void removeSessionGUID(List<String> closedConnections, String sessionGUID) {
        // add sessionGUID to these closedConnections list (in order for the thread to stop)
        // before accessing the closedConnections list lock on it
        synchronized (closedConnections) {
            closedConnections.remove(sessionGUID);
        }
    }

    /**
     * Sends the specified message to the server.
     * <p>
     * AndroidAssembly1 new method
     *
     * @param event the event to encode
     * @param parser the message parser
     * @param output the server's output stream
     * @param logger the logger
     * @param clazz the calling class
     * @param methodName the calling method
     * @throws IOException if error occurred while sending message
     * @throws EventEncodingException if error occurred while encoding event
     */
    public static void send(XREEvent event, MessageParser parser, DataOutputStream output, Logger logger, Object clazz,
        String methodName) throws IOException, EventEncodingException {
        // set timestamp
        event.setTimestamp(System.currentTimeMillis());

        // encode event
        String message = parser.encodeEvent(event);

        // log message
        LogUtil.info(logger, clazz, methodName, "Sending:  {0}", message);

        // send event
        output.writeInt(message.length());
        output.write(message.getBytes());
        output.flush();
    }

    /**
     * Sends the specified message to the server's print writer.
     *
     * @param pw the server's print writer
     * @param msg the message to send
     */
    public static void send(String msg, PrintWriter pw) {
        pw.print(msg);
        pw.flush();
    }

    /**
     * Closes the resource.
     *
     * @param resource the resource to close
     */
    public static void close(Closeable resource) {
        if (null != resource) {
            try {
                resource.close();
            } catch (IOException e) {
                // AndroidAssembly1 ignore exception
            }
        }
    }

    /**
     * Sleeps for given duration in ms.
     *
     * @param duration the duration of sleep
     */
    public static void sleep(int duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            // ignore
        }
    }

    /**
     * Handles the first XRE connect command from the server.
     *
     * @param command the first XRE connect command
     * @param receiver the android receiver to handle the command
     */
    public static void handleFirstCommand(XREConnectCommand command, AndroidReceiver receiver) {
        // pass command to the handler
        receiver.getCommandHandler().onCommandMessage(command, receiver);

        // map session guid to the receiver
        receiver.getApplicationData().setSessionGUID(command.getSessionGUID());
    }
}
